// Created: Dec 14, 2014
//
// Author: wjinwen.1988@gmail.com

#include "chi/net/tcpserver.h"

#include <stdio.h>  // snprintf()
#include <assert.h>

#include <functional>

#include <glog/logging.h>

#include "chi/net/callbacks.h"
#include "chi/net/inet_address.h"
#include "chi/net/socket.h"
#include "chi/net/acceptor.h"
#include "chi/net/event_loop.h"
#include "chi/net/event_loop_thread_pool.h"

namespace chi {
namespace net {

using namespace std::placeholders;

TcpServer::TcpServer(EventLoop* loop, 
                     const InetAddress& listenaddr, 
                     const std::string& name)
  : loop_(loop),
    hostport_(listenaddr.ToIpPort()),
    name_(name),
    acceptor_(new Acceptor(loop, listenaddr)),
    thread_pool_(new EventLoopThreadPool(loop)),
    connection_callback_(DefaultConnectionCallback),
    message_callback_(DefaultMessageCallback),
    started_(false),
    next_connid_(1) {
  acceptor_->set_new_conn_cb(
      std::bind(&TcpServer::NewConnection, this, _1, _2));
}

TcpServer::~TcpServer() {
  if (acceptor_) {
    delete acceptor_;
  }
  if (thread_pool_) {
    delete thread_pool_;
  }
}

void TcpServer::set_num_threads(int num_threads) {
  assert(num_threads >= 0);
  thread_pool_->set_num_threads(num_threads);
}

void TcpServer::Start() {
  if (!started_) {
    started_ = true;
    thread_pool_->Start();
  }
  if (!acceptor_->listenning()) {
    loop_->RunInLoop(
        std::bind(&Acceptor::Listen, acceptor_));
  }
}

void TcpServer::Stop() {
  started_ = false;
  acceptor_->StopListen();
  for (const auto& it : connections_) {
    TcpConnectionPtr conn = it.second;
    conn->loop()->RunInLoop(
        std::bind(&TcpConnection::ConnectDestroyed, conn));
  }
  connections_.clear();
}

void TcpServer::set_connection_callback(const ConnectionCallback& cb) {
  connection_callback_ = cb;
}

void TcpServer::set_message_callback(const MessageCallback& cb) {
  message_callback_ = cb;
}

void TcpServer::NewConnection(int sockfd, const InetAddress& peeraddr) {
  EventLoop* io_loop = thread_pool_->GetNextLoop();
  char buf[32];
  snprintf(buf, sizeof(buf), ":%s#%d", hostport_.c_str(), next_connid_);
  ++next_connid_;
  std::string conn_name = name_ + buf;

  LOG(INFO) << "[" << name_ << "] - new connection [" 
            << conn_name << "] from " << peeraddr.ToIpPort();
  InetAddress localaddr(Socket::GetLocalAddr(sockfd));
  TcpConnectionPtr conn(new TcpConnection(io_loop,
                                          conn_name,
                                          sockfd,
                                          localaddr,
                                          peeraddr));
  connections_[conn_name] = conn;
  conn->set_connection_callback(connection_callback_);
  conn->set_message_callback(message_callback_);
  conn->set_close_callback(
      std::bind(&TcpServer::RemoveConnection, this, _1));
  io_loop->RunInLoop(std::bind(&TcpConnection::ConnectionEstablished, conn));
}

void TcpServer::RemoveConnection(const TcpConnectionPtr& conn) {
  loop_->RunInLoop(std::bind(&TcpServer::RemoveConnectionInLoop, this, conn));
}

void TcpServer::RemoveConnectionInLoop(const TcpConnectionPtr& conn) {
  LOG(INFO) << "remove connection " << conn->name();
  size_t n = connections_.erase(conn->name());
  (void)n;
  assert(n == 1);
  EventLoop* io_loop = conn->loop();
  io_loop->QueueInLoop(std::bind(&TcpConnection::ConnectDestroyed, conn));
}

} 
}

